/*
 * FileName: ObjectHistogramElement.java
 * Author:   zzw
 * Date:     2018年11月19日
 * Description:
 */
package com.zzw.hotspot.sa;

import sun.jvm.hotspot.gc_implementation.parallelScavenge.PSOldGen;
import sun.jvm.hotspot.gc_implementation.parallelScavenge.PSYoungGen;
import sun.jvm.hotspot.gc_implementation.parallelScavenge.ParallelScavengeHeap;
import sun.jvm.hotspot.gc_interface.CollectedHeap;
import sun.jvm.hotspot.memory.DefNewGeneration;
import sun.jvm.hotspot.memory.GenCollectedHeap;
import sun.jvm.hotspot.memory.Generation;
import sun.jvm.hotspot.oops.*;
import sun.jvm.hotspot.runtime.VM;
import sun.jvm.hotspot.utilities.Assert;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;

/**
 * 〈增强版jmap -histo,显示分代信息〉<br>
 * 〈〉
 *
 * @author zzw
 * @see [相关类/方法]（可选）
 * @since [产品/模块版本]（可选）
 */
public class ObjectHistogramElement {
    private Klass klass;
    private long  count; // Number of instances of klass
    private long  size;  // Total size of all these instances
    private long  edenCount;     // Number of instances of klass in eden
    private long  edenSize;      // Total size of all instances in eden
    private long  survivorCount; // ditto in survivor
    private long  survivorSize;  // ditto in survivor
    private long  oldCount;      // ditto in old generation
    private long  oldSize;       // ditto in old generation
    private long  permCount;     // ditto in permanent generation
    private long  permSize;      // ditto in permanent generation
    private long metaCount;
    private long metaSize;

    public ObjectHistogramElement(Klass k) {
        klass = k;
    }

    public void updateWith(Oop obj) {
        long objSize = obj.getObjectSize();
        count = 1;
        size  = objSize;
        if (inEden(obj)) { 
            edenCount = 1;
            edenSize  = objSize;
        } else if (inSurvivor(obj)) {
            survivorCount = 1;
            survivorSize  = objSize;
        } else if (inOld(obj)) {
            oldCount = 1;
            oldSize  = objSize;
        } /*else if (inPerm(obj)) {
            permCount = 1;
            permSize  = objSize;
        } */else {
            throw new RuntimeException("an oop should be in the GC heap");
        }
    }

    public int compare(ObjectHistogramElement other) {
        return (int) size;
    }

    private static boolean inEden(Oop obj) {
        if (obj == null) return false;
        CollectedHeap heap = VM.getVM().getUniverse().heap();
        if (heap instanceof GenCollectedHeap) {
              DefNewGeneration gen0 = (DefNewGeneration) ((GenCollectedHeap) heap).getGen(0);
              return gen0.eden().contains(obj.getHandle());
            } else if (heap instanceof ParallelScavengeHeap) {
              PSYoungGen youngGen = ((ParallelScavengeHeap) heap).youngGen();
              return youngGen.edenSpace().contains(obj.getHandle());
            } else {
              if (Assert.ASSERTS_ENABLED) {
                    Assert.that(false, "Expecting GenCollectedHeap or ParallelScavengeHeap, but got " + heap.getClass().getName());
              }
            }
        return false;
      }

    private static boolean inSurvivor(Oop obj) {
        if (obj == null) return false;
        CollectedHeap heap = VM.getVM().getUniverse().heap();
        if (heap instanceof GenCollectedHeap) {
            DefNewGeneration gen0 = (DefNewGeneration) ((GenCollectedHeap) heap).getGen(0);
            return gen0.from().contains(obj.getHandle());
        }
        else if (heap instanceof ParallelScavengeHeap) {
            PSYoungGen youngGen = ((ParallelScavengeHeap) heap).youngGen();
            return youngGen.fromSpace().contains(obj.getHandle());
        } else {
            if (Assert.ASSERTS_ENABLED) {
                Assert.that(false, "Expecting GenCollectedHeap or ParallelScavengeHeap, but got " + heap.getClass().getName());
            }
        }
        return false;
    }

    private static boolean inOld(Oop obj) {
        if (obj == null) return false;
        CollectedHeap heap = VM.getVM().getUniverse().heap();
        if (heap instanceof GenCollectedHeap) {
            Generation gen1 = ((GenCollectedHeap) heap).getGen(1);
            return gen1.isIn(obj.getHandle());
        }
        else if (heap instanceof ParallelScavengeHeap) {
            PSOldGen oldGen = ((ParallelScavengeHeap) heap).oldGen();
            return oldGen.isIn(obj.getHandle());
        } else {
            if (Assert.ASSERTS_ENABLED) {
                Assert.that(false, "Expecting GenCollectedHeap or ParallelScavengeHeap, but got " + heap.getClass().getName());
            }
        }
        return false;
    }

    //1.7
   /* private static boolean inPerm(Oop obj) {
        if (obj == null) return false;
        CollectedHeap heap = VM.getVM().getUniverse().heap();
        if (heap instanceof GenCollectedHeap) {
              Generation permGen = ((GenCollectedHeap) heap).permGen();
              return permGen.isIn(obj.getHandle());
        }
        else if (heap instanceof ParallelScavengeHeap) {
          PSPermGen permGen = ((ParallelScavengeHeap) heap).permGen();
          return permGen.isIn(obj.getHandle());
        }
        else {
            if (Assert.ASSERTS_ENABLED) {
                Assert.that(false, "Expecting GenCollectedHeap or ParallelScavengeHeap, but got " + heap.getClass().getName());
            }
        }
        return false;
    }*/

    private String getInternalName(Klass k) {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        getKlass().printValueOn(new PrintStream(bos));
        return "* " + bos.toString();
    }

    public static void titleOn(PrintStream tty){
        tty.println("Object Histogram:");
        tty.println();
        tty.println("num " + "\t" + "  #instances" + "\t" + "#bytes" + "\t" + "Class description" +
                "\t" + "eden #instances/#bytes" + "\t" + "survivor #instances/#bytes" + "\t" + "old #instances/#bytes" + "\t" +" perm #instances/#bytes");
        tty.println("-----------------------------------------------------------------------------------");
    }

    public Klass getKlass() {
        return klass;
    }
    /** Human readable description **/
    public String getDescription() {
        Klass k = getKlass();
        if (k instanceof InstanceKlass) {
            return k.getName().asString().replace('/', '.');
        } else if (k instanceof ArrayKlass) {
            ArrayKlass ak = (ArrayKlass) k;
            if (k instanceof TypeArrayKlass) {
                TypeArrayKlass tak = (TypeArrayKlass) ak;
                return tak.getElementTypeName() + "[]";
            } else if (k instanceof ObjArrayKlass) {
                ObjArrayKlass oak = (ObjArrayKlass) ak;
                Klass bottom = oak.getBottomKlass();
                int dim = (int) oak.getDimension();
                StringBuffer buf = new StringBuffer();
                if (bottom instanceof TypeArrayKlass) {
                    buf.append(((TypeArrayKlass) bottom).getElementTypeName());
                } else if (bottom instanceof InstanceKlass) {
                    buf.append(bottom.getName().asString().replace('/', '.'));
                } else {
                    throw new RuntimeException("should not reach here");
                }
                for (int i=0; i < dim; i++) {
                    buf.append("[]");
                }
                return buf.toString();
            }
        }
        return getInternalName(k);
    }

    public void printOn(PrintStream tty){
        tty.print(count + "size");
        tty.print(getDescription());
        tty.print("\t" + edenCount + "/" + edenSize +
                "\t" + survivorCount + "/" + survivorSize +
                "\t" + oldCount + "/" + oldSize +
                "\t" + metaCount + "/" + metaSize);
        tty.println();
    }



}
